package ru.db.jschool;

import java.nio.ByteBuffer;

public class ByteBufferHashMap {
	private ByteBuffer buffer;
	private int capacity;
	private int size;
	private final static int OFFSET_FREE = 0;
	private final static int OFFSET_KEY = 1;
	private final static int OFFSET_VALUE = 2;
	private final static int OFFSET_NEXT = 3;
	
	public ByteBufferHashMap(ByteBuffer buffer, int sizeBytes) {
		this.buffer = buffer;
		this.capacity = sizeBytes / 5 / 4;
	}
	
	public Integer put(int key, int value) {
		int head = buffer.getInt(index(key) * 4);
		
		int next = head;
		int lastNext = head;
		while (next != 0) {
			if (getKey(next) == key) {
				int oldValue = getValue(next);
				setValue(next, value);
				setFree(next, false);
				return oldValue;
			}
			lastNext = next;
			next = getNext(next);
		}
		
		int freeCell = getFreeCell();
		if (freeCell == 0) {
			throw new RuntimeException("Not enough space");
		}
		setKey(freeCell, key);
		setValue(freeCell, value);
		setFree(freeCell, false);
		if (head == 0) {
			buffer.putInt(index(key) * 4, freeCell);
		} else {
			setNext(lastNext, freeCell);
		}
		size++;
		return null;
	}
	
	public Integer get(int key) {
		int head = buffer.getInt(index(key) * 4);
		
		int next = head;
		while (next != 0) {
			if (getKey(next) == key) {
				return getValue(next);
			}
			next = getNext(next);
		}
		return null;
	}
	
	public Integer remove(int key) {
		int head = buffer.getInt(index(key) * 4);
		int next = head;
		int lastNext = head;
		while (next != 0) {
			if (getKey(next) == key) {
				int oldValue = getValue(next);
				setFree(next, true);
				int nextNext = getNext(next);
				setNext(next, 0);
				if (next == head) {
					buffer.putInt(index(key) * 4, nextNext);
				} else {
					setNext(lastNext, getNext(next));
				}
				size--;
				return oldValue;
			}
			lastNext = next;
			next = getNext(next);
		}
		
		return null;
	}
	
	public int getSize() {
		return size;
	}
	
	private void setValue(int offset, int value) {
		buffer.putInt((offset + OFFSET_VALUE) * 4, value);
	}

	private void setKey(int offset, int key) {
		buffer.putInt((offset + OFFSET_KEY) * 4, key);
	}
	
	private void setNext(int offset, int next) {
		buffer.putInt((offset + OFFSET_NEXT) * 4, next);
	}
	
	private void setFree(int offset, boolean free) {
		int f = free ? 0 : 1;
		buffer.putInt((offset + OFFSET_FREE) * 4, f);
	}
	
	private int getKey(int offset) {
		return buffer.getInt((offset + OFFSET_KEY) * 4);
	}
	
	private int getValue(int offset) {
		return buffer.getInt((offset + OFFSET_VALUE) * 4);
	}
	
	private int getNext(int offset) {
		return buffer.getInt((offset + OFFSET_NEXT) * 4);
	}
	
	private boolean getFree(int offset) {
		return buffer.getInt((offset + OFFSET_FREE) * 4) == 0;
	}

	private int getFreeCell() {
		int offset = capacity;
		while (!getFree(offset)) {
			offset+=4;
			if (offset * 4 >= buffer.capacity()) {
				return 0;
			}
		}
		
		return offset;
	}

	private int index(int hash) {
		return Math.abs(hash) % capacity;
	}
	
}
